// ==UserScript==
// @name         HandyPrint 强制登出 + 自动登录（含失败保护，window.name 凭据）
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  清旧 token；登录页自动填写账号密码并登录（凭据来自 window.name），失败不死循环
// @match        https://open-t.handyprint.cn/admin/*
// @match        https://admin.handyprint.cn/admin/*
// @match        https://v2-t.handyprint.cn/admin/login*
// @match        https://handyprint.cn/admin/login*
// @grant        none
// ==/UserScript==
(function () {
  "use strict";

  /* ========== 站点相关配置 ========== */
  const TOKEN_COOKIE = "vue_admin_template_token";
  const LOGIN_PATH = "/admin/login";

  // 登录表单选择器
  const userSel = 'input[name="username"]';
  const passSel = 'input[name="password"]';
  const geeSel = ".geetest_radar_tip"; // 极验初始按钮
  const btnSel = "button.login-button";

  /* ========== sessionStorage 键 ========== */
  const KEY_SHOULD_LOGOUT = "hp_shouldForceLogout";
  const KEY_LOGGED_OUT = "hp_alreadyLoggedOut";
  const KEY_LOGIN_FAIL = "hp_loginFailCount";

  const MAX_FAIL = 3; // 登录失败阈值

  /* ---------- 小工具 ---------- */
  const getCookie = (name) =>
    (document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)")) || [])[2] ||
    null;

  const onLoginPage = () => location.pathname.startsWith(LOGIN_PATH);

  /* ---------- ① 进入页面即判断是否需要强制登出 ---------- */
  (function decideWhetherForceLogout() {
    if (sessionStorage.getItem(KEY_SHOULD_LOGOUT) !== null) return;
    const hasTokenAtStart = !!getCookie(TOKEN_COOKIE);
    sessionStorage.setItem(KEY_SHOULD_LOGOUT, hasTokenAtStart ? "1" : "0");
  })();

  /* ---------- ② 强制登出逻辑 ---------- */
  function tryForceLogout() {
    if (sessionStorage.getItem(KEY_SHOULD_LOGOUT) !== "1") return;
    if (sessionStorage.getItem(KEY_LOGGED_OUT) === "1") return;
    if (onLoginPage()) return;

    const token = getCookie(TOKEN_COOKIE);
    if (!token) return;

    console.log("[HP] 发现旧 token，执行强退 …");

    fetch("https://open-t.handyprint.cn/api/admin/logout", {
      method: "POST",
      credentials: "include",
      headers: {
        Accept: "application/json, text/plain, */*",
        "Content-Type": "application/json",
        "X-Token": token,
      },
    })
      .catch((e) => console.error("[HP] logout 失败：", e))
      .finally(() => {
        document.cookie =
          TOKEN_COOKIE + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/";
        sessionStorage.setItem(KEY_LOGGED_OUT, "1");
        location.replace(LOGIN_PATH + "?loggedout=1");
      });
  }

  /* ---------- ③ 监听 SPA 路由 ---------- */
  function hijackSpaRouting(cb) {
    ["pushState", "replaceState"].forEach((fn) => {
      const raw = history[fn];
      history[fn] = function (...args) {
        raw.apply(this, args);
        cb();
      };
    });
    window.addEventListener("popstate", cb);
  }

  /* ---------- ④ 解析 window.name 凭据 ---------- */
  function getCreds() {
    const raw = window.name || "";
    try {
      const o = JSON.parse(raw);
      if (o?.username && o?.password) return o;
    } catch {}
    // query-string 格式
    const p = new URLSearchParams(raw);
    const u = p.get("username"),
      pw = p.get("password");
    return u && pw ? { username: u, password: pw } : null;
  }

  /* ---------- ⑤ 自动登录（基于 MutationObserver） ---------- */
  function autoLogin() {
    if (!onLoginPage()) return;
    if (getCookie(TOKEN_COOKIE)) return;

    const failCount = +sessionStorage.getItem(KEY_LOGIN_FAIL) || 0;
    if (failCount >= MAX_FAIL) {
      console.warn(`[HP] 登录已连续失败 ${failCount} 次，停止自动登录`);
      return;
    }

    const creds = getCreds();
    if (!creds) {
      console.warn("[HP] window.name 没有有效凭据，跳过自动登录");
      return;
    }

    const observer = new MutationObserver((mutations, obs) => {
      const userInput = document.querySelector(userSel);
      const passInput = document.querySelector(passSel);
      const geeBtn = document.querySelector(geeSel);
      const loginBtn = document.querySelector(btnSel);

      if (userInput && passInput && geeBtn && loginBtn) {
        obs.disconnect();

        // 填值并触发 input/change
        userInput.value = creds.username;
        passInput.value = creds.password;
        ["input", "change"].forEach((ev) => {
          userInput.dispatchEvent(new Event(ev, { bubbles: true }));
          passInput.dispatchEvent(new Event(ev, { bubbles: true }));
        });

        console.log("[HP] 表单已就绪，填充账号密码，点击极验按钮 …");
        geeBtn.click(); // 需要手动拖块

        /* ---- 轮询等待极验通过 & 按钮可用 ---- */
        let tick = 0,
          maxTick = 15;
        const errorSel = ".el-message--error, .error-msg";

        const timer = setInterval(() => {
          tick++;

          // 检测错误提示
          const err = document.querySelector(errorSel);
          if (err) {
            console.warn(
              "[HP] 登录错误提示：",
              (err.innerText || err.textContent).trim()
            );
            clearInterval(timer);
            sessionStorage.setItem(KEY_LOGIN_FAIL, String(failCount + 1));
            return;
          }

          // 按钮可用且极验按钮消失
          const geeExists = document.querySelector(geeSel);
          if (!geeExists && !loginBtn.disabled) {
            console.log("[HP] 极验验证通过，点击登录按钮");
            loginBtn.click();
            clearInterval(timer);
          } else if (tick >= maxTick) {
            console.warn("[HP] 等待登录超时，放弃本轮自动登录");
            sessionStorage.setItem(KEY_LOGIN_FAIL, String(failCount + 1));
            clearInterval(timer);
          }
        }, 1000);
      }
    });

    observer.observe(document.body, { childList: true, subtree: true });
  }

  /* ---------- ⑥ 登录成功后的清理 ---------- */
  function watchForSuccess() {
    const startPath = location.pathname;
    const t = setInterval(() => {
      if (!onLoginPage() && location.pathname !== startPath) {
        console.log("[HP] 登录成功，清理 window.name & 失败计数");
        window.name = "";
        sessionStorage.removeItem(KEY_LOGIN_FAIL);
        clearInterval(t);
      }
    }, 800);
  }

  /* ---------- ⑦ 初始化（整合） ---------- */
  function init() {
    tryForceLogout();
    if (onLoginPage()) {
      autoLogin();
      watchForSuccess();
    }
  }

  /* ---------- 初始化调用 ---------- */
  window.addEventListener("load", init);
  hijackSpaRouting(() => {
    console.log("[HP] 路由变化触发初始化");
    init();
  });
})();
